package com.yunhe.abnormal.service.base.impl;

import com.yunhe.abnormal.client.DeviceClient;
import com.yunhe.common.constant.AbnormalConsts;
import com.yunhe.common.constant.CacheConsts;
import com.yunhe.common.constant.SystemConsts;
import com.yunhe.abnormal.domain.base.AbnormalRule;
import com.yunhe.abnormal.dto.AbnormalRuleDTO;
import com.yunhe.common.exception.ArgumentErrorException;
import com.yunhe.common.exception.ObjectNotFoundException;
import com.yunhe.common.exception.UniqueConstraintsException;
import com.yunhe.common.model.PageParam;
import com.yunhe.common.model.base.DataType;
import com.yunhe.common.model.base.PointDataTypeDTO;
import com.yunhe.common.model.data.AbnormalRuleFormulaObject;
import com.yunhe.common.model.device.Analog;
import com.yunhe.abnormal.repository.DAO.AbnormalRuleRepositoryDao;
import com.yunhe.abnormal.repository.base.AbnormalRuleRepository;
import com.yunhe.abnormal.service.base.AbnormalRuleService;
import com.yunhe.abnormal.transaction.MultiTransaction;
import com.yunhe.common.util.QueryUtil;
import com.yunhe.common.util.RedisClient;
import com.yunhe.common.util.StringUtil;
import net.oschina.j2cache.cache.support.util.SpringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import zju.fes.exp.Calculator;
import zju.fes.exp.IllegalExpressionException;
import zju.fes.exp.IncalculableExpressionException;
import zju.fes.exp.UnknownOperatorException;

import java.util.*;

@Service
public class AbnormalRuleServiceImpl implements AbnormalRuleService {

    @Autowired
    private AbnormalRuleRepository abnormalRuleRepository;
    @Autowired
    private AbnormalRuleRepositoryDao abnormalRuleRepositoryDao;
    @Autowired
    private DeviceClient deviceClient;
    @Autowired
    private RedisClient redisClient;

    private final String FAULT = "Fault";

    private static final Logger logger = LoggerFactory.getLogger(AbnormalRuleServiceImpl.class);

    private void processBeforeSave(AbnormalRule abnormalRule) throws ArgumentErrorException, IncalculableExpressionException,
            IllegalExpressionException, UnknownOperatorException {
        List<AbnormalRuleFormulaObject> formulaObjects = abnormalRule.getAbnormalRuleFormulaObjects();
        //前端做限制
        Map<Long, AbnormalRuleFormulaObject> levelMap = new HashMap<>();
        for (AbnormalRuleFormulaObject formulaObject : formulaObjects) {
            Long levelId = formulaObject.getAlarmLevelId();
            if (levelMap.get(levelId) == null) {
                levelMap.put(levelId, formulaObject);
            } else {
                throw new ArgumentErrorException("存在相同告警等级的配置，请修改");
            }
        }
        //对每个公式进行验证，是否可以执行
        for (AbnormalRuleFormulaObject formulaObject : formulaObjects) {
            String formula = formulaObject.getFormula();
            List<Long> measPointNumbers = formulaObject.getMeasPointNumbers();
            verifyFormula(formula, measPointNumbers);
        }
        abnormalRule.setAbnormalRuleFormulaObjects(null);
        //判断告警状态代码、编号、标题是否为空
        if (StringUtil.isEmpty(abnormalRule.getTitle())) {
            throw new ArgumentErrorException("异常规则标题不能为空");
        }
        //判断同一个对象下告警状态编号是否已存在
        AbnormalRule byTitle = findByTitle(abnormalRule.getTitle(), abnormalRule.getObjectId());
        if (byTitle != null ) {
            throw new ArgumentErrorException("已存在相同标题的告警规则");
        }
        //设置规则本身属性为空 - 不保存
        abnormalRule.setObjectId(null);
        abnormalRule.setObjectTitle(null);
        abnormalRule.setSn(System.currentTimeMillis());
    }

    /**
     * 处理异常公式对象，新建跟异常等级的关系
     * todo 完善虚拟点号新增
     * @param abnormalRule
     * @param formulaObjects
     * @param objectId
     * @param actionType
     */
    private void processAfterSave(AbnormalRule abnormalRule, List<AbnormalRuleFormulaObject> formulaObjects, Optional<Long> objectId, SystemConsts.ACTION actionType) {
        Long abnormalRuleId = abnormalRule.getId();
        if (objectId.isPresent()) {
            //解绑关系并重新绑定
            abnormalRuleRepository.unbindAbnormalRule(abnormalRuleId);
            //绑定关联对象
            abnormalRuleRepository.bindAbnormalRule(abnormalRuleId, objectId.get());
        }
        //先删除所有异常等级关系
        abnormalRuleRepository.deleteAlarmLevelRelationship(abnormalRuleId);
        //再根据传参进行新建关系
        Set<Long> measPointNumbers = new HashSet<>();
        for (AbnormalRuleFormulaObject formulaObject : formulaObjects) {
            measPointNumbers.addAll(formulaObject.getMeasPointNumbers());
            abnormalRuleRepository.createAlarmLevelRelationship(abnormalRuleId, formulaObject.getAlarmLevelId(), formulaObject.getFormula(), formulaObject.getMeasPointNumbers().toString(),
                    formulaObject.getMeasPointNumbers(), formulaObject.getRelatedPointNumbers(), formulaObject.getDescription());
        }
        //新增必传关联对象ID
        if (actionType == SystemConsts.ACTION.ADD) {
            List<Long> measPointNumbersList = new ArrayList<>(measPointNumbers);
            //获取告警点号数据类型
            PointDataTypeDTO virtualAlarm = deviceClient.getPointDataTypeByName(AbnormalConsts.ALARM_POINTDATATYPE.VIRTUALALARM.getName()).getBody().getResults();
            //获取量测点号的点号数据类型
            // - 如果数据类型为告警-Fault，则绑定该点号与故障点号类型
            // - 如果数据类型为null或者不为告警，则新增虚拟点号（根据关联对象获取项目虚拟点号），需要绑定告警规则和关联对象
            List<PointDataTypeDTO> pointDataTypes = deviceClient.getPointDataTypeByPointNumbers(measPointNumbersList).getBody().getResults();
            Boolean faultPdt = false;
            if (pointDataTypes !=null && !pointDataTypes.isEmpty() && pointDataTypes.size() == 1) {
                DataType dataType = pointDataTypes.get(0).getDataType();
                if (dataType.getName().equals(FAULT)) {
                    faultPdt = true;
                }
            }
            if (faultPdt) {
                Analog findByPointNumber = deviceClient.getAnalogByPointNumber(measPointNumbersList.get(0)).getBody().getResults();
                //绑定异常规则,点号,告警点号数据类型
                abnormalRuleRepository.createAnalogRelationship(abnormalRuleId, findByPointNumber.getId(), virtualAlarm.getId(), abnormalRule.getTitle());
            } else {
                //新建点号
                Analog alarmAnalog = new Analog();
                alarmAnalog.setTitle(abnormalRule.getTitle());
                alarmAnalog.setDeviceId(abnormalRuleId);
                alarmAnalog.setTypeId(virtualAlarm.getId());
                alarmAnalog.setTypeName(virtualAlarm.getName());
                alarmAnalog.setTypeTitle(virtualAlarm.getTitle());
                alarmAnalog.setTerminalName(abnormalRule.getTitle());
                alarmAnalog.setSignalTitle(abnormalRule.getTitle());
                deviceClient.createAlarmAnalog(objectId.get(), alarmAnalog);
            }
        }
        //删除告警量测点号枚举缓存
        //删除告警规则公式列表
        redisClient.del(CacheConsts.KEY.ABNORMAL_RULE_MEAS.value());
        redisClient.del(CacheConsts.KEY.FORMULA_OBJECT.value());
    }

    /**
     * 告警规则新增
     * @param abnormalRule
     * @return
     * @throws UniqueConstraintsException
     * @throws ArgumentErrorException
     * @throws ObjectNotFoundException
     */
    @Override
    @MultiTransaction
    public AbnormalRule create(AbnormalRule abnormalRule) throws ArgumentErrorException, IncalculableExpressionException,
            IllegalExpressionException, UnknownOperatorException {
        abnormalRule.setId(null);
        //获取关联对象id
        Long objectId = abnormalRule.getObjectId();
        //获取公式对象
        List<AbnormalRuleFormulaObject> formulaObjects = abnormalRule.getAbnormalRuleFormulaObjects();
        if (formulaObjects == null || formulaObjects.isEmpty()) {
            throw new ArgumentErrorException("异常规则公式对象不能为空");
        }
        //预处理告警规则
        processBeforeSave(abnormalRule);
        AbnormalRule result = abnormalRuleRepository.save(abnormalRule);
        //新增等级关系
        processAfterSave(result, formulaObjects, Optional.of(objectId), SystemConsts.ACTION.ADD);
        return result;
    }

    @Override
    public Iterable<AbnormalRule> create(List<AbnormalRule> abnormalRules) throws ArgumentErrorException, IncalculableExpressionException,
            IllegalExpressionException, UnknownOperatorException {
        List<AbnormalRule> results = new ArrayList<>();
        for (AbnormalRule abnormalRule : abnormalRules) {
            results.add(create(abnormalRule));
        }
        return results;
    }

    @Override
    public AbnormalRule update(AbnormalRule abnormalRule) throws UnknownOperatorException, IncalculableExpressionException, IllegalExpressionException, ArgumentErrorException {
        AbnormalRule byId = this.findById(abnormalRule.getId());
        List<AbnormalRuleFormulaObject> formulaObjects = abnormalRule.getAbnormalRuleFormulaObjects();
        if (formulaObjects == null || formulaObjects.isEmpty()) {
            logger.warn("该异常规则没有对应等级公式");
            return null;
        }
        processBeforeSave(abnormalRule);
        AbnormalRule result = abnormalRuleRepository.save(abnormalRule);
        Long oldObjectId = byId.getObjectId();
        Long newObjectId = abnormalRule.getObjectId();
        if (oldObjectId.equals(newObjectId)) {
            processAfterSave(result, formulaObjects, Optional.empty(), SystemConsts.ACTION.EDIT);
        } else {
            processAfterSave(result, formulaObjects, Optional.of(newObjectId), SystemConsts.ACTION.EDIT);
        }

        return result;
    }

    /**
     * 平台2.5.1更改告警规则等级
     * todo 以后删除
     * @param ids
     * @param alarmLevelId
     */
    @Override
    public void updateLevel(List<Long> ids, Long alarmLevelId) {
        for (Long id : ids) {
            AbnormalRule byId = this.findById(id);
            if (byId == null) {
                continue;
            }
            List<AbnormalRuleFormulaObject> objects = byId.getAbnormalRuleFormulaObjects();
            if (objects !=null && !objects.isEmpty()) {
                for (AbnormalRuleFormulaObject object : objects) {
                    abnormalRuleRepository.deleteAlarmLevelRelationship(id);
                    abnormalRuleRepository.createAlarmLevelRelationship(id, alarmLevelId, object.getFormula(), object.getMeasPointNumbers().toString(),
                            object.getMeasPointNumbers(), object.getRelatedPointNumbers(), object.getDescription());
                }
            }
        }
    }

    @Override
    public AbnormalRule findById(Long id) {
        AbnormalRule abnormalRule = abnormalRuleRepositoryDao.findById(id);
        return abnormalRule;
    }

    /**
     *
     * @param stationIds 查询电站下所有（包含电站，电站下能量单元和设备）异常规则
     * @param objectId 查询对应电站，能量单元，设备关联的异常规则
     * @param queryStr 查询模糊字段
     * @param pageParam 分页参数
     * @return
     */
    @Override
    public Iterable<AbnormalRule> search(Optional<List<Long>> stationIds,
                                         Optional<Long> objectId,
                                         Optional<String> queryStr,
                                         PageParam pageParam) {
        int page = -1;
        int size = -1;
        Boolean needPage = false;
        if (QueryUtil.needPaging(pageParam)) {
            needPage = true;
            page = pageParam.getPage();
            if (page > 0) {
                page = page - 1;
            }
            size = pageParam.getSize();
        }
        List<String> sortList = pageParam.getSort();
        if (sortList == null) {
            sortList = new ArrayList<>();
            sortList.add("-sn");
        } else if (sortList.size() == 0) {
            sortList.add("-sn");
        }
        Map<String, Object> map = abnormalRuleRepositoryDao.getAbnormalRules(stationIds, objectId, queryStr, page, size, sortList);
        List<AbnormalRule> results = (List<AbnormalRule>) map.get("result");
        Long count = ((Number) map.get("count")).longValue();
        if (needPage) {
            Pageable pageable = QueryUtil.getPageRequest(pageParam);
            return new PageImpl<>(results, pageable, count);
        } else {
            return results;
        }
    }

    /**
     * 删除规则时，需要附带删除关联量测点号的异常规则详情缓存和量测点号缓存
     * @param id
     */
    @Override
    public void delete(Long id) {
        abnormalRuleRepository.deleteById(id);
        redisClient.del(CacheConsts.KEY.ABNORMAL_RULE_MEAS.value());
        redisClient.del(CacheConsts.KEY.FORMULA_OBJECT.value());
    }

    @Override
    public AbnormalRule findByName(String name) {
        return abnormalRuleRepository.findByName(name);
    }

    @Override
    public AbnormalRule findByTitle(String title, Long objectId) {
        AbnormalRule abnormalRule = abnormalRuleRepositoryDao.findByTitleAndObject(title, objectId);
        return abnormalRule;
    }

    @Override
    public String getLabel(Long id) {
        return abnormalRuleRepository.getLabelName(id);
    }
  
    /**
     * 根据公式和量测点号，采用随机值对公式完整性进行校验
     * @param formula
     * @param measPointNumbers
     * @throws IncalculableExpressionException
     * @throws IllegalExpressionException
     * @throws UnknownOperatorException
     * @throws ArgumentErrorException
     */
    private void verifyFormula(String formula, List<Long> measPointNumbers) throws IncalculableExpressionException, IllegalExpressionException, UnknownOperatorException, ArgumentErrorException {
        Calculator calculator = new Calculator();
        Map<Long, Double> measPnValue = new HashMap<>();
        for (Long pointNumber : measPointNumbers) {
            measPnValue.put(pointNumber, 0 + Math.random() * (10));
        }
        //尝试随机值验证公式是否执行
        try {
            calculator.eval(formula, transferToNumber(measPnValue)).doubleValue();
        } catch (Exception e) {
            throw new ArgumentErrorException(formula + " " + e.getMessage());
        }
    }

    private Map<String, Number> transferToNumber(Map<Long, Double> map) {
        Map<String, Number> result = new HashMap<>();
        for (Map.Entry entry : map.entrySet()) {
            result.put(String.valueOf(entry.getKey()), (Number) entry.getValue());
        }
        return result;
    }

    /**
     * 获得告警规则id和电站id的映射map
     */
    @Override
    @Cacheable(value = CacheConsts.Region.ALARM_RULE,
            key = "{T(com.yunhe.abnormal.consts.AlarmKey).getDtoMapKey()}")
    public Map<String, AbnormalRuleDTO> getRuleDtoMap() {
            Map<String, AbnormalRuleDTO> result = new HashMap<>();
            List<Map<String, String>> ruleStationList = abnormalRuleRepository.getRuleDtoMap();
            if (ruleStationList != null) {
                for (Map<String, String> item : ruleStationList) {
                    String ruleId = item.get("id");
                    AbnormalRuleDTO abnormalRuleDTO = new AbnormalRuleDTO();
                    abnormalRuleDTO.setStationId(Long.parseLong(item.get("stationId")));
                    abnormalRuleDTO.setId(Long.parseLong(ruleId));
                    abnormalRuleDTO.setObjectId(Long.parseLong(item.get("objectId")));
                    abnormalRuleDTO.setObjectTitle(item.get("objectTitle"));
                    result.put(ruleId, abnormalRuleDTO);
                }
            }
        return result;
    }

    @Override
    @CacheEvict(value = CacheConsts.Region.ALARM_RULE, key = "{T(com.yunhe.abnormal.consts.AlarmKey).getDtoMapKey()}")
    public void deleteRuleDtoMap() {

    }

    /**
     * 根据告警规则id获得电站id
     * @param abnormalRuleId 告警规则id
     */
    @Override
    public Long getStationIdByRuleId(Long abnormalRuleId) {
        if (abnormalRuleId == null) {
            return null;
        }
        Map<String, AbnormalRuleDTO> ruleDtoMap = SpringUtil.getBean(AbnormalRuleService.class).getRuleDtoMap();
        if (ruleDtoMap != null) {
            AbnormalRuleDTO abnormalRuleDTO = ruleDtoMap.get(String.valueOf(abnormalRuleId));
            if (abnormalRuleDTO != null) {
                return abnormalRuleDTO.getStationId();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * 根据告警规则id获得设备id
     * @param abnormalRuleId 告警规则id
     */
    @Override
    public Long getDeviceIdByRuleId(Long abnormalRuleId) {
        if (abnormalRuleId == null) {
            return null;
        }
        Map<String, AbnormalRuleDTO> ruleDtoMap = SpringUtil.getBean(AbnormalRuleService.class).getRuleDtoMap();
        if (ruleDtoMap != null) {
            AbnormalRuleDTO abnormalRuleDTO = ruleDtoMap.get(String.valueOf(abnormalRuleId));
            if (abnormalRuleDTO != null) {
                return abnormalRuleDTO.getObjectId();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * 根据告警规则id获得设备标题
     * @param abnormalRuleId 告警规则id
     */
    @Override
    public String getDeviceTitleByRuleId(Long abnormalRuleId) {
        if (abnormalRuleId == null) {
            return null;
        }
        Map<String, AbnormalRuleDTO> ruleDtoMap = SpringUtil.getBean(AbnormalRuleService.class).getRuleDtoMap();
        if (ruleDtoMap != null) {
            AbnormalRuleDTO abnormalRuleDTO = ruleDtoMap.get(String.valueOf(abnormalRuleId));
            if (abnormalRuleDTO != null) {
                return abnormalRuleDTO.getObjectTitle();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * 根据告警规则id获得规则标题
     * @param abnormalRuleId 告警规则id
     */
    @Override
    public String getRuleTitleByRuleId(Long abnormalRuleId) {
        if (abnormalRuleId == null) {
            return null;
        }
        Map<String, AbnormalRuleDTO> ruleDtoMap = SpringUtil.getBean(AbnormalRuleService.class).getRuleDtoMap();
        if (ruleDtoMap != null) {
            AbnormalRuleDTO abnormalRuleDTO = ruleDtoMap.get(String.valueOf(abnormalRuleId));
            if (abnormalRuleDTO != null) {
                return abnormalRuleDTO.getTitle();
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
}
